package myactest;

import component.MyInstantiationAwareBeanPostProcessor;
import component.MyInstantiationAwareBeanPostProcessor4PostProcessPropertyValues;
import component.MyMergedBeanDefinitionPostProcessor;
import component.MySmartInstantiationAwareBeanPostProcessor;
import component.supplier.MySupplier;
import config.EmptyConfig;
import config.NoPopulateBeanConfig;
import org.junit.Test;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import pojo.InitMethodNameBean;
import pojo.MyMergedBean;
import pojo.TestBean;
import pojo.beforeinstantiation.MyBeforeInstantiation;

/**
 * @author: ZhouBert
 * @date: 2020/9/22
 * @description:	测试 bean 创建过程中的 拓展点
 *
 * 参考链接：
 * https://blog.csdn.net/wangwei19871103/article/details/105168234
 * Spring 5.x 源码之旅三十六之实例提供器和选择构造函数扩展点
 * 哇塞，第一个图真是太完美了！没想到作者跟我想到一块去了！我就是要找 Spring 留给 developer 的拓展点！
 *
 * https://blog.csdn.net/wangwei19871103/article/details/105173363
 * Spring 5.x 源码之旅三十七之MergedBeanDefinitionPostProcessor扩展点
 *
 * https://blog.csdn.net/wangwei19871103/article/details/105174528
 * Spring 5.x 源码之旅三十八之实例化后扩展点
 *
 * https://blog.csdn.net/wangwei19871103/article/details/105175013
 * Spring 5.x 源码之旅三十九之初始化相关扩展点
 */
public class ExpandInBeanCreationTest {

	private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

	/**
	 * 测试一：obtainFromSupplier 实例提供器
	 */
	@Test
	public void testMySupplier(){
		context.register(EmptyConfig.class);
		String myBeanName = "testBean";
		// 开始注册自定义 b.d. 添加自定义 supplier
		context.registerBean(myBeanName, TestBean.class, new MySupplier());
		context.refresh();
		TestBean bean = (TestBean) context.getBean(myBeanName);
		System.out.println("bean = " + bean);
	}

	/**
	 * 测试二：自定义 SmartInstantiationAwareBeanPostProcessor
	 * 可以指定执行的构造函数！
	 * TODO:技术细节：
	 * ？为什么自定义 SmartInstantiationAwareBeanPostProcessor 执行顺序会在 AutowiredAnnotationBeanPostProcessor 之前？
	 * 从 UML 上看，AutowiredAnnotationBeanPostProcessor 是 SmartInstantiationAwareBeanPostProcessor 的孙子，
	 * 中间差了一个 InstantiationAwareBeanPostProcessorAdapter.
	 * i.e.
	 * SmartInstantiationAwareBeanPostProcessor
	 * |
	 * InstantiationAwareBeanPostProcessorAdapter
	 * |
	 * AutowiredAnnotationBeanPostProcessor
	 *
	 * MySmartInstantiationAwareBeanPostProcessor#registerBeanPostProcessors	这个方法里，
	 * registerBeanPostProcessors(beanFactory, internalPostProcessors); 这个在倒数第二行，
	 * internalPostProcessors 里包含的三个 内部后置处理器，放在倒数第二，三，四的位置。
	 * 也就是 CommonAnnotationBeanPostProcessor + AutowiredAnnotationBeanPostProcessor + RequiredAnnotationBeanPostProcessor
	 * 这也是 Spring 构思巧妙地地方。通过先注册用户自定义，然后最后注册 Spring 内部 BeanProcessor
	 * 结合处理流程来看，AbstractAutowireCapableBeanFactory#determineConstructorsFromBeanPostProcessors 方法中，
	 * 遍历流程中，先匹配到自定义的 MySmartInstantiationAwareBeanPostProcessor 发现返回不为 null ,就放弃后面的处理了！
	 *
	 * SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
	 * Constructor<?>[] ctors = ibp.determineCandidateConstructors(beanClass, beanName);
	 * if (ctors != null) {
	 * 	return ctors;
	 * }
	 *
	 * PS.保证规定，最好是实现 PriorityOrdered  接口，保证自定义 SmartInstantiationAwareBeanPostProcessor 的顺序。
	 * 不然，是无法区分自定义后置处理器的执行顺序的！
	 */
	@Test
	public void testMySmartInstantiationAwareBeanPostProcessor(){
		context.register(EmptyConfig.class);
		String testBeanName = "testBean";
		String myBeforeInstantiationBeanName = "myBeforeInstantiation";
		context.getBeanFactory().addBeanPostProcessor(new MySmartInstantiationAwareBeanPostProcessor());
		//先注册 testBean 为了保证参数 bean 存在
		context.registerBean(testBeanName, TestBean.class);
		// 然后注册一个 目标 b.d. 开始了测试
		context.registerBean(myBeforeInstantiationBeanName, MyBeforeInstantiation.class);

		context.refresh();
		MyBeforeInstantiation bean = (MyBeforeInstantiation) context.getBean(myBeforeInstantiationBeanName);
		System.out.println("bean = " + bean.toString());
	}

	/**
	 * 测试三：自定义 MergedBeanDefinitionPostProcessor
	 * 【postProcessMergedBeanDefinition】
	 * TODO:技术细节：
	 * AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors 里
	 * 主要：InitDestroyAnnotationBeanPostProcessor，CommonAnnotationBeanPostProcessor 和 AutowiredAnnotationBeanPostProcessor 处理有注入注解的属性或者方法。
	 *
	 * InitDestroyAnnotationBeanPostProcessor ->	PostConstruct，PreDestroy：
	 * CommonAnnotationBeanPostProcessor ->		Resource，WebServiceRef，EJB
	 * AutowiredAnnotationBeanPostProcessor ->		Autowired，Value
	 * 实际调用的是 postProcessMergedBeanDefinition 方法。
	 *
	 * 在此次拓展中，重写了 postProcessMergedBeanDefinition ：将 注解方法缓存；
	 * 并重写了 postProcessBeforeInitialization 方法：在 bean 初始化之前做的最后一道工序！
	 *
	 */
	@Test
	public void testMyMergedBeanDefinitionPostProcessor(){
		context.register(EmptyConfig.class);
		String myMergedBeanName = "myMergedBean";
		context.registerBean(myMergedBeanName, MyMergedBean.class);

		context.getBeanFactory().addBeanPostProcessor(new MyMergedBeanDefinitionPostProcessor());
		context.refresh();
		//MyMergedBean bean = (MyMergedBean) context.getBean(myMergedBeanName);
	}

	/**
	 * 测试四：自定义 InstantiationAwareBeanPostProcessor
	 * 重写了 postProcessAfterInstantiation 并返回了 false 就会中止 属性的注入！
	 */
	@Test
	public void testMyInstantiationAwareBeanPostProcessor(){
		context.register(NoPopulateBeanConfig.class);
		String noPopulateBeanConfigBeanName = "noPopulateBeanConfig";
		// if 不添加 自定义 InstantiationAwareBeanPostProcessor ->
		// 		bean.toString() = NoPopulateBeanConfig(noPopulateBean=pojo.NoPopulateBean@527740a2)
		// if 添加了以后 ->
		// 		bean.toString() = NoPopulateBeanConfig(noPopulateBean=null)
		// 		说明：即使IOC 有对应的 bean ，但是并没有 populate 到 NoPopulateBeanConfig 中去
		context.getBeanFactory().addBeanPostProcessor(new MyInstantiationAwareBeanPostProcessor());

		context.refresh();
		NoPopulateBeanConfig bean = (NoPopulateBeanConfig) context.getBean(noPopulateBeanConfigBeanName);
		System.out.println("bean.toString() = " + bean.toString());
	}

	/**
	 * 测试五：自定义 InstantiationAwareBeanPostProcessor 为了测试 postProcessPropertyValues
	 * 通过 重写 postProcessPropertyValues 方法，来控制属性注入！
	 *
	 */
	@Test
	public void testMyInstantiationAwareBeanPostProcessor4PostProcessPropertyValues(){
		context.registerBean(NoPopulateBeanConfig.class);
		context.getBeanFactory().addBeanPostProcessor(new MyInstantiationAwareBeanPostProcessor4PostProcessPropertyValues());

		context.refresh();
		NoPopulateBeanConfig bean = (NoPopulateBeanConfig) context.getBean(MyInstantiationAwareBeanPostProcessor4PostProcessPropertyValues.SPECIFIC_NAME);
		System.out.println("bean.toString() = " + bean.toString());
	}

	/**
	 * 测试六：添加自定义 b.d. ，并设置 InitMethodName
	 * ???但是什么时候执行呢
	 *
	 * https://blog.csdn.net/wangwei19871103/article/details/105175013
	 * Spring 5.x 源码之旅三十九之初始化相关扩展点
	 */
	@Test
	public void testInitMethodName(){
		context.registerBean(EmptyConfig.class);

		AnnotatedGenericBeanDefinition annotatedBeanDefinition=new AnnotatedGenericBeanDefinition(InitMethodNameBean.class);
		annotatedBeanDefinition.setInitMethodName(InitMethodNameBean.MY_INITMETHOD_NAME);
		context.registerBeanDefinition(InitMethodNameBean.MY_BEAN_NAME, annotatedBeanDefinition);

		context.refresh();
		InitMethodNameBean bean = (InitMethodNameBean) context.getBean(InitMethodNameBean.MY_BEAN_NAME);
		System.out.println("bean.toString() = " + bean.toString());
	}

}
